﻿using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading;
using Adventure.Library;
using Adventure.Server.Library;
using Neitzel;

namespace Adventure.Server.Manager
{
    /// <summary>
    /// Timer for HeartBeat of objects.
    /// </summary>
    public class HeartBeatTimer : IDisposable
    {
        #region Fields

        /// <summary>
        ///TraceSource to use for logging in this class.
        /// </summary>
        private static TraceSource Logger = new TraceSource(AdventureServerConstants.TraceSourceName);

        /// <summary>
        /// Object used for locking.
        /// </summary>
        private object lockObject = new Object();
         
        /// <summary>
        /// Internal timer.
        /// </summary>
        private Timer _timer;

        /// <summary>
        /// Registered objects of the HeartBeatTimer.
        /// </summary>
        private readonly Collection<AdventureObject> _objects = new Collection<AdventureObject>();

        /// <summary>
        /// Objects that should be removed from the HeartBeatTimer.
        /// </summary>
        private Collection<AdventureObject> _objectsToRemove = new Collection<AdventureObject>();

        /// <summary>
        /// Objects that should be added to the HeartBeatTimer.
        /// </summary>
        private Collection<AdventureObject> _objectsToAdd = new Collection<AdventureObject>();

        /// <summary>
        /// Collection that is used as reserve
        /// </summary>
        private Collection<AdventureObject> _reserveCollection = new Collection<AdventureObject>();

        /// <summary>
        /// Field that shows if the heartbeat is running or not.
        /// </summary>
        private bool _heartbeatRunning = false;

        /// <summary>
        /// Are we disposing this instance at the moment?
        /// </summary>
        private bool _disposing = false;

        #endregion

        #region LifeTime

        /// <summary>
        /// Creates a new instance of HeartBeatTimer.
        /// </summary>
        public HeartBeatTimer()
        {
            _timer = new Timer(HeartBeat);
            _timer.Change(AdventureServerConstants.HeartBeatTime, AdventureServerConstants.HeartBeatTime);
        }

        /// <summary>
        /// Dispose / close this instance.
        /// </summary>
        public void Dispose()
        {
            // full dispose
            Dispose(true);

            // Supress finalize
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Dispose / close this instance.
        /// </summary>
        public void Close()
        {
            Dispose();
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Set the flag that we are disposing now.
                _disposing = true;

                // Dispose the timer.
                _timer?.Dispose();
                _timer = null;

                // Wait for HeartBeat to finish.
                int waited = 0;
                while (_heartbeatRunning && waited < 100)
                {
                    Thread.Sleep(100);
                }

                if (_heartbeatRunning)
                    Logger.Error((int)ServerTraceId.Managers, "Unable to stop running HeartBeat method.");

                // Dispose objects that are inside _objectsToAdd
                foreach (var obj in _objectsToAdd)
                {
                    // remove object from _objects
                    if (_objects.Contains(obj))
                        _objects.Remove(obj);

                    obj.Dispose();
                }

                // Dispose objects that are inside _objects
                foreach (var obj in _objects)
                        obj.Dispose();
            }
        }

        #endregion

        /// <summary>
        /// Call HeartBeat on all objects that are registered.
        /// </summary>
        private void DoHeartBeatForAllObjects()
        {
            foreach (var obj in _objects)
            {
                // Check if we are disposing.
                if (_disposing)
                    return;

                try
                {
                    // check if heartbeat is still active.
                    if (obj.HeartBeatActive)
                    {
                        // Do the HeartBeat.
                        obj.ExecuteHeartBeat(this);
                    }
                    else
                    {
                        // Remove object because HeartBeat is not active.
                        _objectsToRemove.Add(obj);
                    }
                }
                catch (Exception ex)
                {
                    // Log the error and disable Heartbeat for that object.
                    Logger.Error((int)ServerTraceId.Managers, ex, "Error in HeartBeat of object {0}.", obj.Id);
                    _objectsToRemove.Add(obj);
                }
            }
        }

        /// <summary>
        /// Add ojects that are queued inside _objectsToAdd.
        /// </summary>
        private void AddQueuedObjects()
        {
            // Collection used to handle addind and removal of objects.
            Collection<AdventureObject> collection;

            // Lock while switching collections.
            lock (lockObject)
            {
                collection = _objectsToAdd;
                _objectsToAdd = _reserveCollection;
            }

            // Add the objects.
            foreach (var obj in collection)
            {
                // Check if we are disposing.
                if (_disposing)
                    return;

                _objects.Add(obj);
            }

            // Clear collection and use it as reserve
            collection.Clear();
            _reserveCollection = collection;
        }

        /// <summary>
        /// Remove objects that was queued for removal.
        /// </summary>
        private void RemoveQueuedObjects()
        {
            // Collection used to handle addind and removal of objects.
            Collection<AdventureObject> collection;

            // Lock while switching collections.
            lock (lockObject)
            {
                collection = _objectsToRemove;
                _objectsToRemove = _reserveCollection;
            }

            // remove the object if it is inside.
            foreach (var obj in collection)
            {
                // Check if we are disposing.
                if (_disposing)
                    return;

                // Remove object from the object list.
                _objects.Remove(obj);

                // Dispose the object to reduce the reference counter.
                obj.Dispose();
            }

            // Clear collection and use it as reserver
            collection.Clear();
            _reserveCollection = collection;
        }

        /// <summary>
        /// Do the HeartBeat action.
        /// </summary>
        /// <param name="stateInfo">State info.</param>
        protected void HeartBeat(object stateInfo)
        {
            // Check if we are already running within a HeartBeat.
            if (_heartbeatRunning)
            {
                Logger.Warning((int)ServerTraceId.Managers,"Heartbeat skipped because still busy!");
                return;
            }

            try
            {
                DoHeartBeatForAllObjects();

                // Check if we are disposing.
                if (_disposing)
                    return;

                AddQueuedObjects();

                // Check if we are disposing.
                if (_disposing)
                    return;

                RemoveQueuedObjects();
            }
            finally
            {
                _heartbeatRunning = false;
            }
        }

        /// <summary>
        /// Registers an object for HeartBeat.
        /// </summary>
        /// <param name="obj">Object to register for heartbeat.</param>
        public void RegisterObject(AdventureObject obj)
        {
            if (_timer == null)
                throw new AdventureException("HeartBeatTimer is disposed!");

            // Increase the Reference Counter because we store an object.
            obj.IncreaseReferenceCounter();
            lock (lockObject)
            {
                _objectsToAdd.Add(obj);
            }
        }

        /// <summary>
        /// Unregisters an object for HeartBeat.
        /// </summary>
        /// <param name="obj">Object to unregister for heartbeat.</param>
        public void UnregisterObject(AdventureObject obj)
        {
            lock (lockObject)
            {
                _objectsToRemove.Add(obj);
            }
        }
    }
}
